import random
from collections import defaultdict
import numpy as np
import matplotlib.pyplot as plt
from arithmetic.collaborative_learning  import EnhancedCollaborativeLearning

plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False


class VirtualStudent:
    def __init__(self, student_id, start_node, target_node, learning_ability=0.5):
        self.student_id = student_id
        self.start_node = start_node
        self.current_node = start_node
        self.target_node = target_node
        self.learning_ability = np.clip(learning_ability, 0.1, 0.9)
        self.mastered_nodes = set([start_node])
        self.learning_history = []
        self.path_progress = 0

    def study_node(self, node_id, difficulty):
        """引入非线性概率和保底成功率"""
        base_prob = self.learning_ability * (1 - 0.8 * difficulty)  # 降低难度影响
        min_prob = 0.1  # 保底10%成功率
        success_prob = max(base_prob, min_prob)
        return random.random() < success_prob

class TeachingSimulator:
    def __init__(self, neo4j_config):
        self.ta = EnhancedCollaborativeLearning(**neo4j_config)
        self.ta.load_graph()
        self.knowledge_difficulty = self._init_knowledge_difficulty()
        self.students = []
        self.teaching_logs = []
        # 获取最大连通分量
        from networkx import strongly_connected_components
        scc = list(strongly_connected_components(self.ta.G))
        if scc:
            self.largest_scc = max(scc, key=len)
            self.available_nodes = list(self.largest_scc)
        else:
            self.available_nodes = []

    def _init_knowledge_difficulty(self):
        """初始化知识点难度（基于权威值）"""
        difficulties = {}
        for nid in self.ta.G.nodes:
            pr = self.ta.node_attrs[nid]['edu_pr']
            # 调整难度计算公式，pr越高难度越低
            difficulties[nid] = np.clip(1.0 - pr, 0.1, 0.9)
        return difficulties

    def generate_students(self, num_students=10):
        """生成虚拟学生群体，确保起点和终点在最大连通分量中"""
        if not self.available_nodes:
            raise ValueError("图中没有可用的连通节点")
        nodes = self.available_nodes
        max_attempts = 100
        for i in range(num_students):
            attempts = 0
            while True:
                start, target = random.sample(nodes, 2)
                path, _ = self.ta.a_star_search(start, target)
                if path and len(path) >= 2:
                    break
                attempts += 1
                if attempts >= max_attempts:
                    # 处理方式：选择相邻节点
                    start = random.choice(nodes)
                    neighbors = list(self.ta.G.neighbors(start))
                    target = random.choice(neighbors) if neighbors else start
                    break
            ability = random.gauss(0.6, 0.15)
            self.students.append(
                VirtualStudent(
                    student_id=i + 1,
                    start_node=start,
                    target_node=target,
                    learning_ability=ability
                )
            )

    def _update_difficulty(self, node_id, success):
        current_diff = self.knowledge_difficulty[node_id]
        if success:
            delta = -0.1 * (current_diff ** 1.5)  # 高难度时降幅更大
        else:
            delta = 0.03 * (1 - current_diff ** 0.5)  # 高难度时增幅更小
        self.knowledge_difficulty[node_id] = np.clip(current_diff + delta, 0.1, 0.9)

    def single_learning_process(self, student, max_steps=15):
        """单个学生的学习过程模拟"""
        current_path = []
        attempts = 0

        while student.current_node != student.target_node and attempts < max_steps:
            # 获取推荐路径
            path, _ = self.ta.a_star_search(student.current_node, student.target_node)
            if not path or len(path) < 2:
                break  # 无有效路径时终止

            next_node = path[1]
            difficulty = self.knowledge_difficulty[next_node]

            # 模拟学习
            success = student.study_node(next_node, difficulty)
            self._update_difficulty(next_node, success)

            # 记录学习过程
            record = {
                'step': attempts,
                'current': student.current_node,
                'next': next_node,
                'difficulty': difficulty,
                'success': success,
                'path_length': len(path)
            }
            student.learning_history.append(record)

            if success:
                student.current_node = next_node
                student.mastered_nodes.add(next_node)
                current_path = []
            else:
                current_path.append(next_node)
                if len(current_path) > 2:  # 减少失败容忍次数
                    # 回退到已掌握节点中离目标最近的点
                    mastered_nodes = student.mastered_nodes
                    if mastered_nodes:
                        best_fallback = min(
                            mastered_nodes,
                            key=lambda n: len(self.ta.a_star_search(n, student.target_node)[0] or [])
                        )
                        student.current_node = best_fallback
                    current_path = []

            attempts += 1

        # 记录调试信息
        if student.current_node == student.target_node:
            self.debug_info.setdefault('success_students', []).append(student.student_id)
        else:
            self.debug_info.setdefault('fail_students', []).append({
                'id': student.student_id,
                'last_node': student.current_node,
                'attempts': attempts
            })
        return student

    def batch_teaching_experiment(self, num_cycles=10):
        self.debug_info = {
            'difficulty_changes': defaultdict(list),
            'common_fail_nodes': defaultdict(int),
            'cycle_details': []
        }
        metrics = {
            'success_rate': [],
            'avg_steps': [],
            'path_efficiency': []
        }

        print("\n教学实验实时进度:")
        for cycle in range(num_cycles):
            # 生成新一批学生(每周期重置)
            self.generate_students(num_students=20)
            cycle_success = []
            cycle_steps = []
            path_efficiencies = []
            cycle_fail_nodes = defaultdict(int)

            # 本周期教学过程
            for student in self.students:
                self.single_learning_process(student)

                # 记录学习结果
                final_success = student.current_node == student.target_node
                cycle_success.append(final_success)
                cycle_steps.append(len(student.learning_history))

                # 计算路径效率(包含失败尝试)
                if final_success:
                    start_node = student.start_node
                    target_node = student.target_node
                    optimal_path, _ = self.ta.a_star_search(start_node, target_node)
                    if optimal_path and len(optimal_path) >= 2:
                        optimal_steps = len(optimal_path) - 1
                        actual_steps = len(student.learning_history)  # 包含所有尝试
                        if actual_steps > 0:
                            efficiency = optimal_steps / actual_steps
                            path_efficiencies.append(efficiency)

                # 记录失败节点
                if not final_success and student.learning_history:
                    last_fail_node = student.learning_history[-1]['next']
                    cycle_fail_nodes[last_fail_node] += 1

            # 记录全局调试信息
            self.debug_info['common_fail_nodes'].update(cycle_fail_nodes)
            self.debug_info['cycle_details'].append({
                'cycle': cycle,
                'fail_nodes': dict(cycle_fail_nodes)
            })

            # 计算本周期指标
            metrics['success_rate'].append(
                np.mean(cycle_success) if cycle_success else 0
            )
            metrics['avg_steps'].append(
                np.mean(cycle_steps) if cycle_steps else 0
            )
            metrics['path_efficiency'].append(
                np.nanmean(path_efficiencies) if path_efficiencies else 0
            )

            # 记录难度变化趋势
            for node in self.knowledge_difficulty:
                self.debug_info['difficulty_changes'][node].append(
                    self.knowledge_difficulty[node]
                )

            # 动态调整教学策略参数
            self.ta.alpha *= 0.95  # 逐步降低随机探索
            self.teaching_logs.append(metrics.copy())

            # 实时输出关键信息
            top_difficult = sorted(
                self.knowledge_difficulty.items(),
                key=lambda x: x[1],
                reverse=True
            )[:3]
            top_fails = sorted(
                cycle_fail_nodes.items(),
                key=lambda x: x[1],
                reverse=True
            )[:3]

            print(f"\n周期 {cycle + 1}/{num_cycles}")
            print(f"成功率: {metrics['success_rate'][-1]:.2%} | "
                  f"平均步数: {metrics['avg_steps'][-1]:.1f} | "
                  f"效率: {metrics['path_efficiency'][-1]:.2f}")
            print(f"当前难点: " +
                  " | ".join([f"节点{nid}(难度:{diff:.2f})" for nid, diff in top_difficult]))
            print(f"失败热点: " +
                  " | ".join([f"节点{nid}({count}次)" for nid, count in top_fails]))
            print(f"节点0难度变化历史: {simulator.debug_info['difficulty_changes'].get(0, [])[-3:]}")
            print(
                f"节点590被尝试次数: {sum(1 for s in self.students for entry in s.learning_history if entry['next'] == 590)}")
            # 重置学生群体
            self.students = []

        return metrics

    def visualize_results(self, metrics):
        """可视化教学效果（三线同坐标系+双Y轴版）"""
        plt.figure(figsize=(10, 5))

        # 主坐标系设置
        ax = plt.gca()
        ax.set_xlabel('教学周期', fontsize=11, labelpad=8)
        ax.spines['top'].set_visible(False)

        # 绘制成功率（左Y轴）
        line1, = ax.plot(metrics['success_rate'],
                         'o-',
                         color='#2ca02c',
                         markersize=6,
                         linewidth=2,
                         label='成功率')

        # 绘制路径效率（左Y轴）
        line2, = ax.plot(metrics['path_efficiency'],
                         'D-',
                         color='#ff7f0e',
                         markersize=5,
                         linewidth=2,
                         label='路径效率')

        # 左轴配置
        ax.set_ylabel('比率值', fontsize=11, labelpad=10)
        ax.set_ylim(0, 1.05)
        ax.yaxis.set_major_formatter(plt.FormatStrFormatter('%.1f'))
        ax.grid(True, axis='y', alpha=0.3)

        # 创建右Y轴（平均步数）
        ax2 = ax.twinx()
        line3, = ax2.plot(metrics['avg_steps'],
                          's--',
                          color='#1f77b4',
                          markersize=5,
                          linewidth=2,
                          alpha=0.9,
                          label='平均步数')

        # 右轴配置
        ax2.set_ylabel('学习步数', fontsize=11, labelpad=10)
        ax2.spines['top'].set_visible(False)
        ax2.spines['right'].set_linewidth(0.5)
        ax2.grid(True, axis='y', alpha=0.3)

        # 智能设置Y轴范围
        max_steps = max(metrics['avg_steps']) * 1.1
        ax2.set_ylim(0, max_steps if max_steps > 5 else 10)

        # 合并图例
        lines = [line1, line2, line3]
        ax.legend(lines,
                  [l.get_label() for l in lines],
                  loc='upper center',
                  bbox_to_anchor=(0.5, 1.15),
                  ncol=3,
                  fontsize=10,
                  frameon=False)

        # 全局样式优化
        plt.title('教学效果多维趋势分析', fontsize=13, pad=50)
        ax.tick_params(axis='both', which='major', labelsize=9)
        ax2.tick_params(axis='both', which='major', labelsize=9)

        # 对齐刻度线
        plt.setp(ax.get_xticklabels(), ha='center')

        plt.tight_layout()
        plt.savefig('teaching_metrics_unified.png', dpi=150, bbox_inches='tight')
        plt.close()

class TeachingAnalysis:
    """教学效果分析模块"""

    @staticmethod
    def analyze_learning_patterns(logs, simulator):
        pattern_report = {}
        # 计算平均指标，过滤NaN值
        pattern_report['avg_success_rate'] = np.nanmean([x['success_rate'][-1] for x in logs])
        pattern_report['avg_efficiency'] = np.nanmean([x['path_efficiency'][-1] for x in logs])

        # 识别最佳实践周期
        success_rates = [x['success_rate'][-1] for x in logs]
        best_cycle = np.nanargmax(success_rates)
        pattern_report['best_params'] = {
            'alpha': 0.85 * (0.95 ** best_cycle),
            'feedback_strength': 0.1 + best_cycle * 0.02
        }

        # 使用simulator访问调试信息
        diff_changes = simulator.debug_info['difficulty_changes']
        pattern_report['difficulty_analysis'] = {
            'most_improved': TeachingAnalysis._find_most_changed(diff_changes, threshold=0.1),
            'most_difficult': TeachingAnalysis._find_hardest_nodes(simulator)
        }

        # 学习路径分析
        pattern_report['path_analysis'] = {
            'common_failures': TeachingAnalysis._find_common_failures(simulator),
            'avg_retry_attempts': np.nanmean([s['attempts'] for s in simulator.debug_info.get('fail_students', [])])
        }

        return pattern_report

    @staticmethod
    def _find_most_changed(diff_data, threshold=0.1):
        """找出难度变化最大的知识点"""
        changes = {}
        for node, history in diff_data.items():
            if len(history) >= 2:
                delta = history[-1] - history[0]
                if abs(delta) > threshold:
                    changes[node] = delta
        return dict(sorted(changes.items(), key=lambda x: x[1], reverse=True)[:3])

    @staticmethod
    def _find_hardest_nodes(simulator):
        """找出最终难度最高的节点"""
        return dict(sorted(simulator.knowledge_difficulty.items(),
                           key=lambda x: x[1], reverse=True)[:3])

    @staticmethod
    def _find_common_failures(simulator):
        """找出常见失败节点"""
        failures = simulator.debug_info['common_fail_nodes']
        return dict(sorted(failures.items(), key=lambda x: x[1], reverse=True)[:3])


if __name__ == "__main__":
    # Neo4j 配置
    neo4j_config = {
        "neo4j_uri": "bolt://localhost:7687",
        "user": "neo4j",
        "password": "123456789",
        "alpha": 0.85,
        "feedback_strength": 0.1
    }

    # 运行教学模拟实验
    simulator = TeachingSimulator(neo4j_config)
    final_metrics = simulator.batch_teaching_experiment(num_cycles=10)

    # 可视化结果
    simulator.visualize_results(final_metrics)

    # 生成分析报告
    analysis = TeachingAnalysis.analyze_learning_patterns(simulator.teaching_logs, simulator)

    print("\n=== 深度分析报告 ===")
    print(f"• 知识掌握情况: 成功学生 {len(simulator.debug_info['success_students'])} 人，"
          f"失败 {len(simulator.debug_info['fail_students'])} 人")
    print(f"• 平均失败尝试次数: {analysis['path_analysis']['avg_retry_attempts']:.1f} 次")

    print("\n[难度变化分析]")
    for node, delta in analysis['difficulty_analysis']['most_improved'].items():
        trend = "下降" if delta < 0 else "上升"
        print(f"→ 节点 {node}: 难度{trend} {abs(delta):.2f}")

    print("\n[难点聚焦]")
    for node, diff in analysis['difficulty_analysis']['most_difficult'].items():
        print(f"→ 节点 {node}: 最终难度值 {diff:.2f}")

    print("\n[常见学习障碍]")
    for node, count in analysis['path_analysis']['common_failures'].items():
        print(f"→ 节点 {node}: 累计失败 {count} 次")
    # 保存详细日志
    with open('teaching_logs.txt', 'w') as f:
        for idx, log in enumerate(simulator.teaching_logs):
            f.write(f"教学周期 {idx + 1}:\n")
            f.write(f"  成功率: {log['success_rate'][-1]:.2%}\n")
            f.write(f"  平均步数: {log['avg_steps'][-1]:.1f}\n")
            f.write(f"  路径效率: {log['path_efficiency'][-1]:.2f}\n\n")